package com.shangjinrong.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shangjinrong.domain.*;
import com.shangjinrong.domain.bo.TransFlowBO;
import com.shangjinrong.domain.vo.BorrowInfoApprovalVO;
import com.shangjinrong.domain.vo.BorrowerDetailVO;
import com.shangjinrong.enums.LendStatusEnum;
import com.shangjinrong.enums.ReturnMethodEnum;
import com.shangjinrong.enums.TransTypeEnum;
import com.shangjinrong.excption.BusinessException;
import com.shangjinrong.hfb.HfbConst;
import com.shangjinrong.hfb.RequestHelper;
import com.shangjinrong.mapper.BorrowerMapper;
import com.shangjinrong.mapper.LendMapper;
import com.shangjinrong.mapper.UserAccountMapper;
import com.shangjinrong.mapper.UserInfoMapper;
import com.shangjinrong.result.ResponseEnum;
import com.shangjinrong.service.*;
import com.shangjinrong.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Naruto
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {
    @Autowired
    private LendItemService lendItemService;

    @Resource
    private DictService dictService;

    @Resource
    private BorrowerMapper borrowerMapper;

    @Resource
    private BorrowerService borrowerService;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private UserAccountMapper userAccountMapper;


    @Resource
    private LendReturnService lendReturnService;

    @Resource
    private LendItemReturnService lendItemReturnService;

    @Autowired
    private TransFlowService transFlowService;

    @Override
    public IPage<Lend> findPage(IPage page, String search) {
        return baseMapper.selectPage(page, search);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVo, BorrowInfo borrowInfo) {
        Lend lend = new Lend();

        lend.setUserId(borrowInfo.getUserId());
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setLendNo(LendNoUtils.getLendNo());
        lend.setTitle(borrowInfoApprovalVo.getTitle());
        lend.setAmount(borrowInfo.getAmount());
        lend.setPeriod(borrowInfo.getPeriod());
        lend.setLendYearRate(borrowInfoApprovalVo.getLendYearRate().divide(new BigDecimal(100)));
        lend.setServiceRate(borrowInfoApprovalVo.getServiceRate().divide(new BigDecimal(100)));
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal(100));
        lend.setInvestAmount(new BigDecimal(0));
        lend.setInvestNum(0);
        lend.setPublishDate(new Date());


        //标的开始时间也就是起息时间
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate lendStartDate = LocalDate.parse(borrowInfoApprovalVo.getLendStartDate(), dtf);

        lend.setLendStartDate(Date.from(lendStartDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant()));

        LocalDate lendEndDate = lendStartDate.plusMonths(borrowInfo.getPeriod());

        //结束时间等于开始时间加上借款期数（月）
        lend.setLendEndDate(Date.from(lendEndDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant()));

        lend.setLendInfo(borrowInfoApprovalVo.getLendInfo());

        //月利率=年利率/12
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, RoundingMode.DOWN);
        //平台预计收益=标的金额*月利率*借款期数
        BigDecimal expectAmount = borrowInfo.getAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));

        lend.setExpectAmount(expectAmount);

        lend.setRealAmount(new BigDecimal(0));

        lend.setStatus(LendStatusEnum.INVEST_RUN.getCode().byteValue());

        lend.setCheckTime(new Date());

        lend.setCheckAdminId(1L);

        baseMapper.insert(lend);
    }

    @Override
    public Map<String, Object> getLendDetail(Long id) {
        Lend lend = baseMapper.selectById(id);

        String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod().intValue());
        String status = LendStatusEnum.getMsgByStatus(lend.getStatus().intValue());

        HashMap<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("returnMethod", returnMethod);
        paramsMap.put("status", status);

        lend.setParams(paramsMap);


        //借款人详细信息
        QueryWrapper<Borrower> borrowerQueryWrapper = new QueryWrapper<>();

        borrowerQueryWrapper
                .eq("user_id", lend.getUserId());

        Borrower borrower = borrowerMapper.selectOne(borrowerQueryWrapper);

        BorrowerDetailVO borrowerDetailVo = borrowerService.getBorrowerDetailVoById(borrower.getId());

        Map<String, Object> map = new HashMap<>();

        map.put("lend", lend);
        map.put("borrower", borrowerDetailVo);

        return map;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void makeLoan(Long id) {
        Lend lend = baseMapper.selectById(id);

        //判断标的状态是否为结标,不为结标抛出异常
        if (lend.getStatus() != LendStatusEnum.FINISH.getCode().intValue()) {
            throw new BusinessException(ResponseEnum.LEND_STATUS_NOT_FINISH_ERROR);
        }

        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("agentId", HfbConst.AGENT_ID);
        paramMap.put("agentProjectCode", lend.getLendNo());
        paramMap.put("agentBillNo", LendNoUtils.getLoanNo());

        //借款人实际放款金额=借款金额-平台收益
        BigDecimal serviceMonthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, RoundingMode.DOWN);


        //平台收益
        BigDecimal realAmount = lend.getInvestAmount().multiply(serviceMonthRate).multiply(new BigDecimal(lend.getPeriod()));

        paramMap.put("mchFee", realAmount);
        paramMap.put("timestamp", RequestHelper.getTimestamp());
        paramMap.put("sign", RequestHelper.getSign(paramMap));

        log.info("放款参数：{}", JSONObject.toJSONString(paramMap));

        //发送同步远程调用
        JSONObject result = RequestHelper.sendRequest(paramMap, HfbConst.MAKE_LOAD_URL);

        log.info("放款结果：{}", result.toJSONString());

        //放款失败
        String resultCode = result.getString("resultCode");
        if (!"0000".equals(resultCode)) {
            throw new BusinessException(result.getString("resultMsg"));
        }

        //更新标的信息
        lend.setStatus(LendStatusEnum.PAY_RUN.getCode().byteValue());
        lend.setRealAmount(realAmount);
        lend.setPaymentTime(new Date());
        baseMapper.updateById(lend);

        //获取借款人信息
        Long userId = lend.getUserId();

        UserInfo userInfo = userInfoMapper.selectById(userId);

        String bindCode = userInfo.getBindCode();

        //给借款人账户转入金额
        BigDecimal total = new BigDecimal(result.getString("voteAmt"));
        userAccountMapper.updateAccount(bindCode, total, new BigDecimal(0));

        //新增借款人交易流水  后续补充

        //获取该标的投资人列表
        List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId());

        lendItemList.stream().forEach(lendItem -> {

            //获取投资人信息
            Long investUserId = lendItem.getInvestUserId();
            UserInfo investUserInfo = userInfoMapper.selectById(investUserId);
            String investUserInfoBindCode = investUserInfo.getBindCode();

            //投资人金额转出
            BigDecimal investAmount = lendItem.getInvestAmount();

            //investAmount.negate()   取反，前提是该值为非负数
            //余额不变，冻结金额-投资金额

            BigDecimal voteAmt = new BigDecimal(result.getString("voteAmt"));

            userAccountMapper.updateAccount(investUserInfoBindCode, new BigDecimal(0), investAmount.negate());

            String agentBillNo = result.getString("agentBillNo");

            //新增投资人交易流水，后续补充
            TransFlowBO transFlowBO = new TransFlowBO(
                    agentBillNo,
                    bindCode,
                    voteAmt,
                    TransTypeEnum.BORROW_BACK,
                    "项目放款，项目编号：" + lend.getLendNo() + "，项目名称：" + lend.getTitle()
            );

            transFlowService.saveTransFlow(transFlowBO);


        });

        //放款成功，生成还款计划
        repaymentPlan(lend);
    }

    @Override
    public List<Lend> getLendList() {

        List<Lend> lendList = baseMapper.selectList(null);

        lendList.forEach(lend -> {
            String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", lend.getReturnMethod().intValue());
            String status = LendStatusEnum.getMsgByStatus(lend.getStatus());

            Map<String, Object> map = new HashMap<>();
            map.put("returnMethod", returnMethod);
            map.put("status", status);

            lend.setParams(map);
        });

        return lendList;
    }
    //计算总利息
    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {
        BigDecimal interestCount;
        //计算总利息
        if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod()) {
            interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalmonth);
        } else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod()) {
            interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalmonth);
        } else if(returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod()) {
            interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalmonth);
        } else {
            interestCount = Amount4Helper.getInterestSum(invest, yearRate, totalmonth);
        }
        return interestCount;
    }

    /**
     * 生成还款计划
     *
     * @param lend
     */
    private void repaymentPlan(Lend lend) {
        //还款计划列表
        List<LendReturn> lendReturnList = new ArrayList<>();

        //根据换款期数创建对应的还款计划
        for (int i = 0; i < lend.getPeriod().intValue(); i++) {
            LendReturn lendReturn = new LendReturn();

            //填充基本属性
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            lendReturn.setBaseAmount(lend.getInvestAmount());
            lendReturn.setCurrentPeriod(i + 1);
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());

            lendReturn.setFee(new BigDecimal(0));
            //还款时间，从放款的下一个月还
            LocalDate localDate = DateUtils.date2LocalDate(lend.getLendStartDate()).plusMonths(i + 1);

            lendReturn.setReturnDate(DateUtils.localDate2Date(localDate));
            lendReturn.setIsOverdue(false);

            //判断是否是最后一期
            lendReturn.setIsLast(i + 1 == lend.getPeriod().intValue());

            //设置还款状态
            lendReturn.setStatus((byte) 0);

            lendReturnList.add(lendReturn);
        }


        //批量保存还款计划
        lendReturnService.saveBatch(lendReturnList);


        //生成还款期数和还款记录的id对应的键值对集合  Map<CurrentPeriod,Id>

        Map<Integer, Long> lendReturnMap = lendReturnList.stream().collect(
                Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId)
        );

        //创建所有投资的所有回款记录列表
        List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();

        //获取当前标的下已支付的所有投资列表
        List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId(), 1);

        for (LendItem lendItem : lendItemList) {
            //根据投资记录的id调用回款计划生成的方法，得到当前这笔投资的回款计划列表
            List<LendItemReturn> lendItemReturnList = this.returnInvest(lendItem.getId(), lendReturnMap, lend);
            lendItemReturnAllList.addAll(lendItemReturnList);
        }


        for (LendReturn lendReturn : lendReturnList) {
            //获取当前期数所有投资人的本金，利息，总金额 并相加，就是当前期数借款人要还的本金，利息，总金额
            BigDecimal sumPrincipal = lendItemReturnAllList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal sumInterest = lendItemReturnAllList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            BigDecimal sumTotal = lendItemReturnAllList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
                    .map(LendItemReturn::getTotal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            lendReturn.setPrincipal(sumPrincipal);
            lendReturn.setInterest(sumInterest);
            lendReturn.setTotal(sumTotal);

        }
        //批量更新
        lendReturnService.updateBatchById(lendReturnList);
    }


    /**
     * 生成回款计划（每一笔投资的回款计划）
     *
     * @param lendItemId
     * @param lendReturnMap
     * @param lend
     * @return
     */
    private List<LendItemReturn> returnInvest(Long lendItemId, Map<Integer, Long> lendReturnMap, Lend lend) {
        //获取当前投资记录信息
        LendItem lendItem = lendItemService.getById(lendItemId);

        //投资金额
        BigDecimal investAmount = lendItem.getInvestAmount();
        //年利率
        BigDecimal lendYearRate = lendItem.getLendYearRate();
        //总期数
        Integer totalMonth = lend.getPeriod();

        //Map<期数,利息>
        Map<Integer, BigDecimal> mapInterest = null;
        //Map<期数,本金>
        Map<Integer, BigDecimal> mapPrincipal = null;

        //根据还款方式计算本金和利息
        if (lend.getReturnMethod().intValue() == ReturnMethodEnum.ONE.getMethod()) {
            //利息
            mapInterest = Amount1Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            //本金
            mapPrincipal = Amount1Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.TWO.getMethod()) {
            //利息
            mapInterest = Amount2Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            //本金
            mapPrincipal = Amount2Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.THREE.getMethod()) {
            //利息
            mapInterest = Amount3Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            //本金
            mapPrincipal = Amount3Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        } else if (lend.getReturnMethod().intValue() == ReturnMethodEnum.FOUR.getMethod()) {
            //利息
            mapInterest = Amount4Helper.getPerMonthInterest(investAmount, lendYearRate, totalMonth);
            //本金
            mapPrincipal = Amount4Helper.getPerMonthPrincipal(investAmount, lendYearRate, totalMonth);
        }

        //回款计划列表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();

        for (Map.Entry<Integer, BigDecimal> entry : mapInterest.entrySet()) {
            Integer currentPeriod = entry.getKey();

            //根据当前期数，获取还款计划的id
            Long lendReturnId = lendReturnMap.get(currentPeriod);

            //创建回款计划记录
            LendItemReturn lendItemReturn = new LendItemReturn();

            lendItemReturn.setLendReturnId(lendReturnId);
            lendItemReturn.setLendItemId(lendItemId);
            lendItemReturn.setLendId(lend.getId());
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            lendItemReturn.setCurrentPeriod(currentPeriod);
            lendItemReturn.setLendYearRate(lendItem.getLendYearRate());
            lendItemReturn.setReturnMethod(lend.getReturnMethod());

            //将本金，利息，本息插入   这是最后一期
            if (currentPeriod.intValue() == lend.getPeriod()) {

                //***************************本金*******************************************

                //前几期的本金之和
                BigDecimal sumPrincipal = lendItemReturnList.stream()
                        .map(LendItemReturn::getPrincipal)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                //最后一期本金=总投资额-前几期的本金
                BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(sumPrincipal);
                lendItemReturn.setPrincipal(lastPrincipal);

                //***************************利息*******************************************
                BigDecimal sumInterest = lendItemReturnList.stream()
                        .map(LendItemReturn::getInterest)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                BigDecimal lastInterest = lendItem.getExpectAmount().subtract(sumInterest);

                lendItemReturn.setInterest(lastInterest);
            } else {
                //非最后一期

                //***************************本金*******************************************
                lendItemReturn.setPrincipal(mapPrincipal.get(currentPeriod));
                //***************************利息*******************************************
                lendItemReturn.setInterest(mapInterest.get(currentPeriod));
            }
            //回款总金额==本金+利息
            lendItemReturn.setTotal(lendItemReturn.getInterest().add(lendItemReturn.getPrincipal()));

            lendItemReturn.setFee(new BigDecimal(0));

            //时间处理，回款日期=标的开始日期月数+当前期数
            LocalDate localDate = DateUtils.date2LocalDate(lend.getLendStartDate());
            Date date = DateUtils.localDate2Date(localDate.plusMonths(currentPeriod));
            //回款日期
            lendItemReturn.setReturnDate(date);
            //是否逾期
            lendItemReturn.setIsOverdue(false);

            lendItemReturn.setStatus((byte) 0);

            //将回款记录添加到回款计划列表
            lendItemReturnList.add(lendItemReturn);
        }

        //批量保存
        lendItemReturnService.saveBatch(lendItemReturnList);

        return lendItemReturnList;
    }
}
